﻿using Developer.Core.FrameWork.ClusterLoadBalance.Service;
using Developer.Core.FrameWork.RegisterCenter.Model;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Developer.Core.FrameWork.ClusterLoadBalance.Impl
{
    /// <summary>
    /// 加权随机v2
    /// </summary>
    public class WeightLoadBalance : AbstractLoadBalance
    {
        private readonly ConcurrentDictionary<string, WeightItem> _serviceDic = new ConcurrentDictionary<string, WeightItem>();
        private readonly SpinLock _spinLock = new SpinLock();
        public override ServiceNode DoSelect(IList<ServiceNode> serviceUrls, string serviceName)
        {
            if (serviceUrls == null)
                return null;

            if (serviceUrls.Count == 1)
                return serviceUrls[0];

            bool _lock = false;
            this._spinLock.Enter(ref _lock);

            WeightItem weightItem = null;
            if (!this._serviceDic.ContainsKey(serviceName))
            {
                weightItem = new WeightItem()
                {
                    Index = -1,
                    Urls = new List<ServiceNode>()
                };

                BuildWeightItem(weightItem, serviceUrls.ToList());

                this._serviceDic.TryAdd(serviceName, weightItem);
            }
            else
            {
                this._serviceDic.TryGetValue(serviceName, out weightItem);
                weightItem.Urls.Clear();
                BuildWeightItem(weightItem, serviceUrls.ToList());
            }

            ServiceNode node = new ServiceNode();
            ++weightItem.Index;
            if (weightItem.Index > weightItem.Urls.Count() - 1)
            {
                weightItem.Index = 0;
                node = serviceUrls[0];
            }
            else
            {
                node = weightItem.Urls[weightItem.Index];
            }

            this._serviceDic[serviceName] = weightItem;

            if (_lock)
                this._spinLock.Exit();

            return node;
        }

        private void BuildWeightItem(WeightItem weightItem, List<ServiceNode> serviceNodes)
        {
            serviceNodes.ForEach(service =>
            {
                int weight = 1;
                if (service != null && service.Url != "")
                    int.TryParse(service.ServiceTags[0], out weight);

                for (int i = 0; i < weight; i++)
                    weightItem.Urls.Add(new ServiceNode()
                    {
                        Url = service.Url,
                        ServiceAddress = service.ServiceAddress,
                        ServiceID = service.ServiceID,
                        ServiceName = service.ServiceName,
                        ServicePort = service.ServicePort,
                        ServiceTags = service.ServiceTags
                    });
            });
        }
    }

    public class WeightItem
    {
        public List<ServiceNode> Urls { get; set; }
        public int Index { get; set; }
    }
}
